<?php

namespace App\Service;
/**
 * 麻将基本胡牌 （不包含小七对）
 *
 * 此类检测是否胡牌，返回胡牌的组合, 你可以再将获得的胡牌组合，进行相应牌型检测获得牌型
 * 不能用于小七对的胡牌检测
 *
 */
class Hupai
{

    // 万 筒 条 东南西北 中发白 花
    private $cardTypes = [
        'wan' => [1, 2, 3, 4, 5, 6, 7, 8, 9],
        'tong' => [11, 12, 13, 14, 15, 16, 17, 18, 19],
        'tiao' => [21, 22, 23, 24, 25, 26, 27, 28, 29],
        'dnxb' => [31, 32, 33, 34],
        'zfb' => [35, 36, 37],
        'flower' => []
    ];


    private $stack = [];    // 牌组栈
    private $cards = [];    // 剩余手牌
    private $pairs = 0;    // 将对个数
    private $raw = false;    // 是否直接返牌组栈
    private $number = [];    // 牌堆

    private $is4d = false;    // 包含4对子特殊牌型

    //特殊牌列组合执行对应的算法
    private $matchArr = [
        '113333' => 2, //顺刻对
        '322133' => 3, //对顺刻
        '3111332' => 4, //刻顺对
        '32111111111' => 5, //刻对顺
        '311333' => 3, //对顺刻
        '331211111' => 2, //顺刻对
        '1112111222' => 7, //对顺
        '1111113113' => 3, //对顺刻
        '111111111311' => 3, //对顺刻
        '2222111111' => 3, //对顺刻
    ];

    //types麻将牌种类:['wan','tong','tiao','dnxb','zfb','flower']
    public function __construct(array $types = [], bool $raw = false)
    {
        if (empty($types)) {
            $number = [
                1, 2, 3, 4, 5, 6, 7, 8, 9,
                11, 12, 13, 14, 15, 16, 17, 18, 19,
                21, 22, 23, 24, 25, 26, 27, 28, 29
            ];
        } else {
            foreach ($types as $v) {
                if (isset($this->cardTypes[$v])) {
                    $number = array_merge($this->cardTypes[$v]);
                }
            }
        }
        $this->number = $number;
        $this->raw = $raw;
    }

    public function clear(bool $raw = false)
    {
        $this->stack = [];
        $this->cards = [];
        $this->pairs = 0;
        $this->raw = $raw;
    }

    public function start(array $cards, array $peng = [], array $gang = [])
    {
        //牌数小于14或者大于18，则必定不能胡牌
        $num = count($cards);
        $pengNum = count($peng) * 3;
        $gangNum = count($gang) * 4;
        $totalNum = $num + $pengNum + $gangNum;
        if (($totalNum < 14) || ($totalNum > 18)) {
            return [];
        }
        //去除碰杠的牌
        sort($cards);
        $this->cards = $cards;

//        dd('sort_cards', $this->cards);

        // 特殊牌数：1，1，3，3，3，3
        $countValuesArr = array_count_values($this->cards);
//        dd('countValuesArr', $countValuesArr);
        // 杠牌
        if (($num - 14) > 0) {
            $this->try_quadra($countValuesArr);
        } else {
            //七对
            if (empty($peng) && empty($gang) && $this->is_qidui($countValuesArr)) {
//                ddNode(4);
                return array_chunk($this->cards, 2);
            }
        }

        $res = $this->checkhu($cards);
        if ($res) {
            foreach ($res as $k => $v) {
                if (empty($v)) {
                    unset($res[$k]);
                }
            }
            return $res;
        }
        $matchStr = implode(array_values($countValuesArr));

        if (isset($this->matchArr[$matchStr])) {
            $method = 'try_hu' . $this->matchArr[$matchStr];
            if ($this->$method()) {
                if ($this->raw) {
                    // 直接返回牌组栈
                    return $this->stack;
                } else {
                    // 返回胡牌的排列
                    $win_cards = [];
                    foreach ($this->stack as $e) {
                        $win_cards[] = $e;
                    }
                    return $win_cards;
                }
            }
        }
        //去除碰杠的牌
        $this->cards = $cards;
        sort($this->cards);

        $this->check4D($countValuesArr);
        if ($this->try_hu()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        $this->cards = $cards;

        //顺子刻子对子
        if ($this->try_hu2()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        $this->cards = $cards;

        //对子顺子刻子
        if ($this->try_hu3()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        $this->cards = $cards;

        //刻子顺子对子
        if ($this->try_hu4()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        $this->cards = $cards;

        //刻子对子顺子
        if ($this->try_hu5()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        $this->cards = $cards;

        //顺子对子
        if ($this->try_hu6()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        $this->cards = $cards;

        //对子顺子
        if ($this->try_hu7()) {
            if ($this->raw) {
                // 直接返回牌组栈
                return $this->stack;
            } else {
                // 返回胡牌的排列
                $win_cards = [];
                foreach ($this->stack as $e) {
                    $win_cards[] = $e;
                }
                return $win_cards;
            }
        }
        return [];
    }

    // 将牌组压栈
    private function push(array $group)
    {
        array_push($this->stack, $group);
    }

    // 牌组出栈
    private function roll_back()
    {
        $group = array_pop($this->stack);
        array_push($this->cards, $group);
        sort($this->cards);
    }

    // 杠，则将此组牌压栈
    private function try_quadra(array $countValuesArr)
    {
//        dd('rs', $rs);
        foreach ($countValuesArr as $k => $v) {
            if ($v == 4) {
                $this->cards = $this->array_remove($this->cards, $k, 4);
//                dd('this->cards', $this->cards);
                $this->push([$k, $k, $k, $k]);
//                dd('this->stack', $this->stack);
            }
        }
    }

    // 刻子，则将此组牌压栈
    private function try_triplet(int $card)
    {
        if ($this->array_count($this->cards, $card) >= 3) {
            $this->cards = $this->array_remove($this->cards, $card, 3);
            $this->push([$card, $card, $card]);
            return true;
        } else
            return false;
    }

    // 顺子，则将此组牌压栈
    private function try_along(int $card)
    {
        if (!in_array($card, $this->number)) {
            return false;
        }
        if (in_array($card, $this->cards) && in_array($card + 1, $this->cards) && in_array($card + 2, $this->cards)) {
            $this->cards = $this->array_remove($this->cards, $card);
            $this->cards = $this->array_remove($this->cards, $card + 1);
            $this->cards = $this->array_remove($this->cards, $card + 2);
            $this->push([$card, $card + 1, $card + 2]);
            return true;
        } else
            return false;
    }

    // 对子，对子只能有一个
    private function try_sub(int $card)
    {
        if ($this->pairs == 1)
            return false;

        if ($this->array_count($this->cards, $card) >= 2) {
            $this->cards = $this->array_remove($this->cards, $card, 2);
            $this->push([$card, $card]);
            $this->pairs = 1;
            return true;
        } else
            return false;
    }


    // 回溯尝试组成顺子刻子对子
    private function try_hu()
    {
//        ddNode('h1');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }

//        dd('cards', $this->cards);
        $active_card = reset($this->cards);
        //找刻子
        if ($this->try_triplet($active_card)) {
//            ddNode('k');
            if (!$this->try_hu())
                $this->roll_back();
            else
                return true;
        }

        if ($this->is4d) {
            //找顺子
            if ($this->try_along($active_card)) {
//                ddNode('s');
                if (!$this->try_hu())
                    $this->roll_back();
                else
                    return true;
            }
            //找对子
            if ($this->try_sub($active_card)) {
//                ddNode('d');
//                ddNode("< $active_card >");
                if (!$this->try_hu()) {
                    $this->roll_back();
                    $this->pairs = 0;
                } else
                    return true;
            }
        } else {
            //找对子
            if ($this->try_sub($active_card)) {
//                ddNode('d');
//                ddNode("< $active_card >");
                if (!$this->try_hu()) {
                    $this->roll_back();
                    $this->pairs = 0;
                } else
                    return true;
            }
            //找顺子
            if ($this->try_along($active_card)) {
//                ddNode('s');
                if (!$this->try_hu())
                    $this->roll_back();
                else
                    return true;
            }
        }


        return false;
    }

    // 回溯尝试组成顺子刻子对子
    private function try_hu2()
    {
//        ddNode('h2');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }
        $active_card = reset($this->cards);

        //找顺子
        if ($this->try_along($active_card)) {
            if (!$this->try_hu2())
                $this->roll_back();
            else
                return true;
        }
        //找刻子
        if ($this->try_triplet($active_card)) {
            if (!$this->try_hu2())
                $this->roll_back();
            else
                return true;
        }
        //找对子
        if ($this->try_sub($active_card)) {
            if (!$this->try_hu2()) {
                $this->roll_back();
                $this->pairs = 0;
            } else
                return true;
        }
        return false;
    }


    // 回溯尝试组成对子顺子刻子
    private function try_hu3()
    {
//        ddNode('h3');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }
        $active_card = reset($this->cards);

        //找对子
        if ($this->try_sub($active_card)) {
            if (!$this->try_hu3()) {
                $this->roll_back();
                $this->pairs = 0;
            } else
                return true;
        }

        //找顺子
        if ($this->try_along($active_card)) {
            if (!$this->try_hu3())
                $this->roll_back();
            else
                return true;
        }
        //找刻子
        if ($this->try_triplet($active_card)) {
            if (!$this->try_hu3())
                $this->roll_back();
            else
                return true;
        }

        return false;
    }


    // 回溯尝试组成刻子顺子对子
    private function try_hu4()
    {
//        ddNode('h4');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }
        $active_card = reset($this->cards);

        //找刻子
        if ($this->try_triplet($active_card)) {
            if (!$this->try_hu4())
                $this->roll_back();
            else
                return true;
        }

        //找顺子
        if ($this->try_along($active_card)) {
            if (!$this->try_hu4())
                $this->roll_back();
            else
                return true;
        }

        //找对子
        if ($this->try_sub($active_card)) {
            if (!$this->try_hu4()) {
                $this->roll_back();
                $this->pairs = 0;
            } else
                return true;
        }

        return false;
    }

    // 回溯尝试组成刻子对子顺子
    private function try_hu5()
    {
//        ddNode('h5');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }
        $active_card = reset($this->cards);

        //找刻子
        if ($this->try_triplet($active_card)) {
            if (!$this->try_hu5())
                $this->roll_back();
            else
                return true;
        }

        //找对子
        if ($this->try_sub($active_card)) {
            if (!$this->try_hu5()) {
                $this->roll_back();
                $this->pairs = 0;
            } else
                return true;
        }

        //找顺子
        if ($this->try_along($active_card)) {
            if (!$this->try_hu5())
                $this->roll_back();
            else
                return true;
        }
        return false;
    }

    // 回溯尝试组成顺子对子
    private function try_hu6()
    {
//        ddNode('h6');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }
        $active_card = reset($this->cards);
        //找顺子
        if ($this->try_along($active_card)) {
            if (!$this->try_hu6())
                $this->roll_back();
            else
                return true;
        }
        //找对子
        if ($this->try_sub($active_card)) {
            if (!$this->try_hu6()) {
                $this->roll_back();
                $this->pairs = 0;
            } else
                return true;
        }
        return false;
    }

    // 回溯尝试组成对子顺子
    private function try_hu7()
    {
//        ddNode('h7');
        if (!$this->cards) {
            if ($this->pairs == 1)
                return true;
            else
                return false;
        }
        $active_card = reset($this->cards);
        //找对子
        if ($this->try_sub($active_card)) {
            if (!$this->try_hu7()) {
                $this->roll_back();
                $this->pairs = 0;
            } else
                return true;
        }
        //找顺子
        if ($this->try_along($active_card)) {
            if (!$this->try_hu7())
                $this->roll_back();
            else
                return true;
        }

        return false;
    }


    private function array_remove(array $cards, int $card, int $d = 1)
    {
        foreach ($cards as $k => $v) {
            if ($v == $card && $d) {
                $d--;
                unset($cards[$k]);
            }
        }
        return $cards;
    }

    private function array_count(array $cards, int $card)
    {
        @$rs = array_count_values($cards);
        foreach ($rs as $k => $v) {
            if ($card == $k) return $v;
        }
        return 0;
    }

    private function is_qidui(array $countValuesArr)
    {
        foreach ($countValuesArr as $k => $v) {
            if ($v % 2 != 0) return false;
        }
        return true;
    }

    // 校验是否包含4对子，
    private function check4D(array $countValuesArr)
    {
        $num = 0;
        foreach ($countValuesArr as $k => $v) {
            if ($v == 2) {
                $num += 1;
            }
        }
        $this->is4d = $num == 4 ? true : false;
    }

    public function checkhu($cards)
    {
        $jiang = [];
        $rs = array_count_values($cards);
        //找出适合做将的牌
        foreach ($rs as $card => $num) {
            if ($num >= 2) {
                $jiang[] = $card;
            }
        }
//        dd('jiang', $jiang);
        //依次根据找出的将牌数据进行筛选，顺子和刻子的组合
        foreach ($jiang as $card) {
            $shun = [];
            $ke = [];
            $tem = $rs;
            $tem[$card] -= 2; //减掉将牌数量
            //默认按照顺子 > 刻子的顺序查找
            foreach ($tem as $c => $v) {
                //最大可能出现的同一组顺子为4组，所以要找4次
                if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                    $shun[] = [$c, $c + 1, $c + 2];
                    $tem[$c] -= 1;
                    $tem[$c + 1] -= 1;
                    $tem[$c + 2] -= 1;
                }
                if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                    $shun[] = [$c, $c + 1, $c + 2];
                    $tem[$c] -= 1;
                    $tem[$c + 1] -= 1;
                    $tem[$c + 2] -= 1;
                }
                if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                    $shun[] = [$c, $c + 1, $c + 2];
                    $tem[$c] -= 1;
                    $tem[$c + 1] -= 1;
                    $tem[$c + 2] -= 1;
                }
                if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                    $shun[] = [$c, $c + 1, $c + 2];
                    $tem[$c] -= 1;
                    $tem[$c + 1] -= 1;
                    $tem[$c + 2] -= 1;
                }
                if ($tem[$c] == 3) { //找出刻子
                    $ke[] = [$c, $c, $c];
                    $tem[$c] -= 3;
                }
                if ($tem[$c] == 4) { //找出大刻子
                    $ke[] = [$c, $c, $c, $c];
                    $tem[$c] -= 4;
                }
            }

            if (array_sum($tem)) { //手牌牌面值求和大于0，表示还有剩牌，换个方式继续查找
                $shun = [];
                $ke = [];
                $tem = $rs;
                $tem[$card] -= 2; //减掉奖牌数量
                foreach ($tem as $c => $v) {
                    //再次按照 刻子 > 顺子 的顺序查找
                    if ($tem[$c] == 3) { //找出刻子
                        $ke[] = [$c, $c, $c];
                        $tem[$c] -= 3;
                    }
                    if ($tem[$c] == 4) { //找出大刻子
                        $ke[] = [$c, $c, $c, $c];
                        $tem[$c] -= 4;
                    }
                    //最大可能出现的同一组顺子为4组，所以要找4次
                    if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                        $shun[] = [$c, $c + 1, $c + 2];
                        $tem[$c] -= 1;
                        $tem[$c + 1] -= 1;
                        $tem[$c + 2] -= 1;
                    }
                    if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                        $shun[] = [$c, $c + 1, $c + 2];
                        $tem[$c] -= 1;
                        $tem[$c + 1] -= 1;
                        $tem[$c + 2] -= 1;
                    }
                    if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                        $shun[] = [$c, $c + 1, $c + 2];
                        $tem[$c] -= 1;
                        $tem[$c + 1] -= 1;
                        $tem[$c + 2] -= 1;
                    }
                    if ($tem[$c] > 0 && (isset($tem[$c + 1]) && $tem[$c + 1] > 0) && (isset($tem[$c + 2]) && $tem[$c + 2] > 0)) { //找出顺子
                        $shun[] = [$c, $c + 1, $c + 2];
                        $tem[$c] -= 1;
                        $tem[$c + 1] -= 1;
                        $tem[$c + 2] -= 1;
                    }
                }
            }
            if (!array_sum($tem)) { //手牌牌面值求和等于0，表示没有剩牌，已经胡了
                $huArr = array_merge($shun, $ke);
                $huArr[] = [$card, $card];
                return $huArr;
            }
        }
        return false;
    }


}